Skip to content

Conversation

@BhagyeshPatil2004
Copy link

@BhagyeshPatil2004 BhagyeshPatil2004 commented Jan 24, 2026

Error Message & Validation Localization Engine

This PR adds a community demo under /community that showcases how Lingo.dev can be used to dynamically localize backend error and validation messages.

This submission is part of the community demo initiative.
Closes #1761

🎯 Overview

A production-grade demo showcasing automatic backend error localization. This addresses a common pain point: most applications translate UI labels but leave backend errors in English.

🚀 What This Demo Does

This project demonstrates dynamic backend error localization - intercepting validation errors before they reach the client and translating them to the user's preferred language in real-time.

Key Features:

  • Automatic Language Detection - Supports both ?lang=xx query parameters and Accept-Language headers
  • Real-time Translation - Powered by Lingo.dev SDK with AI-driven localization
  • Multi-language Support - Tested with English, Spanish, French, German, Hindi, and Japanese
  • Production-Ready Pattern - Clean middleware architecture that scales to any language
  • Beautiful UI - Modern frontend with gradient design and smooth animations
  • Zero Manual Translation Files - No hardcoded translations needed

🛠️ Tech Stack

  • Backend: Node.js + Express
  • Localization: Lingo.dev SDK (lingo.dev/sdk)
  • Frontend: Vanilla HTML/CSS/JS (framework-agnostic)

📂 Project Structure

community/error-localization-engine
├── server.js # Express server with CORS
├── lingoClient.js # Lingo.dev SDK wrapper
├── middleware/localizer.js # Language detection middleware
├── routes/auth.js # Auth endpoints with validation
└── public/ # Frontend UI
‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ├── index.html
‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ├── styles.css
‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ └── app.js

🧪 How to Test

  1. npm install
  2. Add API key to .env
  3. npm start
  4. Open http://localhost:3000
  5. Select a language and try invalid inputs!

💡 Why This Matters

Most developers focus on UI localization but forget about backend errors. This demo shows a scalable pattern for:

  • Centralizing error messages
  • Dynamic localization without manual translation files
  • Maintaining consistency across all user touchpoints

Demo Screenshots :

image‎ ‎ ‎ ‎ ‎ ‎

Screenshot 2026-01-24 213936 ‎ ‎ ‎ ‎ ‎ ‎ Screenshot 2026-01-24 213704 ‎ ‎ ‎ ‎ ‎ ‎ Screenshot 2026-01-24 213809 ‎ ‎ ‎ ‎ ‎ ‎ Screenshot 2026-01-24 214019

Summary by CodeRabbit

  • New Features

    • Added a demo app with language selector, signup/login UI, localized error and success messages, and a verification script to exercise localization.
  • Documentation

    • Added a comprehensive README and community demo notes with setup, usage, and API test examples (including mock/localization scenarios).
  • Chores

    • Added environment example, .gitignore, package manifest, and frontend assets (HTML/CSS/JS) to support the demo.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

📝 Walkthrough

Walkthrough

Adds a complete error localization demo under community/error-localization-engine: Node.js/Express app, Lingo.dev client with mock fallback, language-negotiation middleware, localized auth routes, static frontend and styles, docs, verification script, package manifest, and .gitignore/.env.example.

Changes

Cohort / File(s) Summary
Configuration & Ignore
community/error-localization-engine/.env.example, community/error-localization-engine/.gitignore, community/error-localization-engine/package.json
Add env example (LINGO_API_KEY, PORT), gitignore entries (node_modules/, .env, *.log, .DS_Store), and package manifest with start/dev scripts and dependencies.
Lingo client
community/error-localization-engine/lingoClient.js
New module exporting localizeError(content, targetLocale); initializes Lingo.dev SDK when LINGO_API_KEY present, otherwise uses mock-mode prefixing; handles string/object payloads and falls back to original content on errors.
Middleware
community/error-localization-engine/middleware/localizer.js
New Express middleware that resolves req.targetLang from req.query.lang, Accept-Language header, or defaults to 'en'; normalizes inputs and logs the chosen language.
Routes / Auth
community/error-localization-engine/routes/auth.js
New auth router with POST /api/auth/signup and POST /api/auth/login; performs validation, uses an in-memory user store, and localizes all messages via localizeError.
Server & Integration
community/error-localization-engine/server.js
New Express app wiring CORS, JSON/static serving, localizer middleware, auth routes, and a localized root endpoint; exports app and starts server when run directly.
Frontend
community/error-localization-engine/public/index.html, community/error-localization-engine/public/app.js, community/error-localization-engine/public/styles.css
Add static signup UI with language selector, client-side form submission to /api/auth/signup?lang=..., response display logic, and responsive styling.
Docs & Verification
community/error-localization-engine/README.md, community/error-localization-engine/verify.js, .changeset/add-error-localization-demo.md
Add README with setup and API examples, verification script running 3 localization tests (es/fr/en) with mock/live handling, and a changeset documenting the demo addition.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as User/Browser
    participant Frontend as Frontend App
    participant Server as Express Server
    participant Localizer as Localizer Middleware
    participant Auth as Auth Route
    participant LingoClient as Lingo Client
    participant LingoAPI as Lingo.dev API

    Browser->>Frontend: select language & submit signup form
    Frontend->>Server: POST /api/auth/signup?lang=es (email,password)
    Server->>Localizer: incoming request
    Localizer->>Server: set req.targetLang = "es"
    Server->>Auth: validate input
    alt validation fails
        Auth->>LingoClient: localizeError(errorMessage,"es")
        LingoClient->>LingoAPI: request translation (or mock)
        LingoAPI-->>LingoClient: translated message
        LingoClient-->>Auth: localized message
        Auth-->>Server: 4xx response (localized)
    else validation passes
        Auth->>LingoClient: localizeError(successMessage,"es")
        LingoClient->>LingoAPI: request translation (or mock)
        LingoAPI-->>LingoClient: translated message
        LingoClient-->>Auth: localized message
        Auth-->>Server: 200 response (localized)
    end
    Server-->>Frontend: JSON response (localized)
    Frontend-->>Browser: display localized message
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through files to help translate,
Errors now chatter in tongues I elate,
Mocked prefixes or real API sway,
Signup messages brightened today,
Hooray — localized in a playful way.

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is comprehensive and covers the overview, features, tech stack, project structure, testing instructions, and includes demo screenshots. However, it does not follow the required template structure with explicit sections for Summary, Changes, Testing checklist, and Visuals checklist. Align the description with the template format by clearly marking Summary, Changes, Testing, and Visuals sections with proper checklist items for test coverage and no breaking changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding a community demo for error localization engine with issue reference.
Linked Issues check ✅ Passed The PR meets all core requirements from issue #1761: the demo is added under /community/error-localization-engine directory with a comprehensive README, clear setup instructions, and documentation of Lingo.dev SDK features used.
Out of Scope Changes check ✅ Passed All changes are directly related to adding the error localization engine demo under /community. The only tangential file is .changeset/add-error-localization-demo.md which documents the change and is appropriate for the repository.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@community/error-localization-engine/middleware/localizer.js`:
- Around line 7-20: Normalize the derived lang before assigning req.targetLang:
if req.query.lang is an array or object, coerce it to a string by taking the
first element/value; when parsing req.headers['accept-language'], split on ','
then drop any quality suffix by splitting on ';' and trimming the result;
lowercase or otherwise normalize the token (e.g., toLowerCase()) and optionally
validate it against a simple BCP-47-ish pattern before assigning to
req.targetLang; finally fall back to 'en' if the normalized value is empty or
invalid. Ensure you update the logic around req.query.lang, acceptLang (the
variable from req.headers['accept-language']), and the final assignment to
req.targetLang.

In `@community/error-localization-engine/README.md`:
- Around line 40-90: Add a short clarifying note under the "🧪 Testing the API"
section (before the examples for English/Spanish/French) that explains if
LINGO_API_KEY is not set the server runs in mock mode and localized responses
may be mock-prefixed (e.g., "[es] ..." or "[fr] ...") rather than fully
translated; mention that real translations are returned when LINGO_API_KEY is
provided and that language detection still follows the `?lang=xx` query
parameter and `Accept-Language` header priority rules already described.

In `@community/error-localization-engine/routes/auth.js`:
- Around line 73-78: The token is being passed into localizeError (see
localizeError call creating successMsg) which may try to translate it; change
the flow so you first call localizeError with only translatable fields (e.g.,
message) to get the localized payload, then attach the non-translatable token
field (e.g., token: "fake-jwt-token-123456") onto the returned object before
sending res.json, ensuring localizeError is not given the token.

In `@community/error-localization-engine/verify.js`:
- Around line 1-72: The script currently hardcodes port 3000 and always expects
mock-prefixed localization; update runTests to require('dotenv').config(), build
baseURL using const port = process.env.PORT || 3000 and const baseURL =
`http://127.0.0.1:${port}/api/auth`, and detect mock mode via a boolean like
const isMock = !process.env.LINGO_API_KEY || process.env.LINGO_API_KEY ===
'your_lingo_api_key_here'; then adjust the localization assertions (in the POST
/signup error checks and success check) to only expect the mock prefix
'[es]'/'[fr]' when isMock is true, otherwise validate that translated messages
exist without assuming the mock prefix.
🧹 Nitpick comments (4)
community/error-localization-engine/package.json (1)

6-9: Declare a minimum Node version for node --watch.

The dev script depends on node --watch, which isn’t available in older Node LTS. Consider adding an engines.node constraint (or a fallback dev script) to prevent confusing runtime errors.

♻️ Suggested adjustment
   "scripts": {
     "start": "node server.js",
     "dev": "node --watch server.js"
   },
+  "engines": {
+    "node": ">=18.11"
+  },
   "dependencies": {
community/error-localization-engine/server.js (1)

29-34: Move dynamic require to module level.

The require('./lingoClient') inside the route handler is evaluated on every request. Move it to the top with other imports for efficiency.

Suggested fix
 const localizer = require('./middleware/localizer');
 const authRoutes = require('./routes/auth');
+const { localizeError } = require('./lingoClient');
 
 const app = express();

Then update the route:

 // Root endpoint
 app.get('/', async (req, res) => {
-    const { localizeError } = require('./lingoClient');
     const msg = await localizeError("Welcome to the Error Localization Engine Demo API!", req.targetLang);
     res.send(msg);
 });
community/error-localization-engine/public/app.js (1)

17-24: Consider encoding the language parameter.

While the current language values (en, es, fr, de, hi, ja) are safe, using encodeURIComponent is a good defensive practice for query parameters.

Suggested fix
-        const response = await fetch(`/api/auth/signup?lang=${language}`, {
+        const response = await fetch(`/api/auth/signup?lang=${encodeURIComponent(language)}`, {
community/error-localization-engine/lingoClient.js (1)

32-42: Mock mode doesn't handle nested objects.

The mock translation only processes top-level string values. Nested objects are copied as-is, which may produce inconsistent behavior between mock and API modes. This is acceptable for the demo's use case (flat error objects), but worth documenting.

For a more robust mock (optional enhancement):

Recursive mock translation
+    const mockTranslateObject = (obj) => {
+        const translated = {};
+        for (const [key, value] of Object.entries(obj)) {
+            if (typeof value === 'string') {
+                translated[key] = mockTranslate(value);
+            } else if (typeof value === 'object' && value !== null) {
+                translated[key] = mockTranslateObject(value);
+            } else {
+                translated[key] = value;
+            }
+        }
+        return translated;
+    };
+
     if (isString) {
         return mockTranslate(content);
     } else {
-        const translated = {};
-        for (const [key, value] of Object.entries(content)) {
-            if (typeof value === 'string') {
-                translated[key] = mockTranslate(value);
-            } else {
-                translated[key] = value;
-            }
-        }
-        return translated;
+        return mockTranslateObject(content);
     }

@sumitsaurabh927
Copy link
Contributor

hey, your PR is missing changeset. Please add the changeset by following instructions here. Make sure you've joined our discord to see this message. If you haven't you need to:

  1. Join our discord here
  2. Go through the instructions to add changeset and add it to your PR

Please also sign your commits

Please also go through the comments.

- Add changeset describing community demo addition
- Fix language normalization in middleware (handle arrays and quality values)
- Prevent token field from being localized in auth routes
- Add mock mode documentation note in README
- Read PORT from .env instead of hardcoding 3000
- Detect mock mode based on LINGO_API_KEY presence
- Adjust localization assertions for both mock and live modes
@github-actions
Copy link
Contributor

Thank you for your contribution! However, this PR references issue #1761, but you're not currently assigned to it. Please get assigned to the issue first, or find an unassigned issue to work on from our issue tracker.

@github-actions github-actions bot closed this Jan 27, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@community/error-localization-engine/verify.js`:
- Around line 9-17: The live-mode branch of checkLocalization currently returns
true for any non-empty msg (isMockMode false), allowing missing translations to
slip through; change checkLocalization (and callers if needed) so that in live
mode it compares msg against the English baseline (e.g., fetch the English text
via an englishMessages map or getEnglishMessage(lang) and ensure msg !==
englishMsg and msg is non-empty), or alternatively update the function signature
to accept an englishBaseline parameter and use that comparison instead of
unconditionally returning true; keep the isMockMode path unchanged.
🧹 Nitpick comments (1)
community/error-localization-engine/verify.js (1)

19-87: Verification can pass on unexpected status codes and never fails the process.
Right now any error response (even 500) can still report PASS, and the script exits with code 0. This makes automated verification unreliable. Consider tracking failures, asserting expected 400s, and setting a non‑zero exit code.

🛠️ Suggested tightening of verification behavior
 async function runTests() {
-    
+    let failed = false;
+
     console.log('--- Starting Verification ---');
@@
-        if (err.response) {
+        if (err.response) {
+            if (err.response.status !== 400) {
+                failed = true;
+                console.log(`❌ FAIL: Expected 400, got ${err.response.status}`);
+            }
             console.log('Response Status:', err.response.status);
             console.log('Response Body:', err.response.data);
             const msg = err.response.data.message || '';
             if (checkLocalization(msg, 'es')) {
                 console.log('✅ PASS: Localized to Spanish');
             } else {
+                failed = true;
                 console.log('❌ FAIL: Not localized correctly');
             }
         } else {
+            failed = true;
             console.error('❌ FAIL: Network/Server Error:', err.message);
         }
     }
@@
-        if (err.response) {
+        if (err.response) {
+            if (err.response.status !== 400) {
+                failed = true;
+                console.log(`❌ FAIL: Expected 400, got ${err.response.status}`);
+            }
             console.log('Response Status:', err.response.status);
             console.log('Response Body:', err.response.data);
              const msg = err.response.data.message || '';
             if (checkLocalization(msg, 'fr')) {
                 console.log('✅ PASS: Localized to French');
             } else {
+                failed = true;
                 console.log('❌ FAIL: Not localized correctly');
             }
         } else {
+            failed = true;
             console.error('❌ FAIL: Network/Server Error:', err.message);
         }
     }
@@
         if (res.data.message && !res.data.message.includes('[')) {
              console.log('✅ PASS: English default (no mock prefix)');
         } else {
+             failed = true;
              console.log('❌ FAIL: Unexpected content in success message');
         }
     } catch (err) {
+        failed = true;
         console.error('❌ FAIL: Unexpected Error:', err.response ? err.response.data : err.message);
     }
+
+    if (failed) process.exitCode = 1;
 }

Comment on lines +9 to +17
function checkLocalization(msg, lang) {
if (!msg) return false;
if (isMockMode) {
return msg.includes(`[${lang}]`);
}
// In live mode, just check that message exists and is different from English
console.log(`ℹ️ Live mode detected; skipping mock-prefix assertion for ${lang}.`);
return true;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Live-mode localization check always passes.
In live mode, checkLocalization returns true for any non-empty message (Line 14-16), so the test won’t catch missing translations. Consider comparing against an English baseline (or adjust the comment/behavior to avoid false positives).

🤖 Prompt for AI Agents
In `@community/error-localization-engine/verify.js` around lines 9 - 17, The
live-mode branch of checkLocalization currently returns true for any non-empty
msg (isMockMode false), allowing missing translations to slip through; change
checkLocalization (and callers if needed) so that in live mode it compares msg
against the English baseline (e.g., fetch the English text via an
englishMessages map or getEnglishMessage(lang) and ensure msg !== englishMsg and
msg is non-empty), or alternatively update the function signature to accept an
englishBaseline parameter and use that comparison instead of unconditionally
returning true; keep the isMockMode path unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build Demo Apps, Integrations etc & Win Exclusive Lingo.dev Swag!

2 participants